<?php

namespace app\api\controller;

use app\api\library\Err;
use think\Request;
use app\common\controller\ApiController;
use think\Cache;
use think\Config;
use think\Env;
use Tos\TosClient;

/**
 * 微信JSSDK接口
 */
class Wechat extends ApiController
{
    protected $noNeedLogin = ['getJsConfig'];
    protected $noNeedRight = '*';

    /**
     * 获取微信JSSDK配置
     *
     * @param Request $request
     * @return \think\Response
     */
    public function getJsConfig(Request $request)
    {
        $url = $request->param('url', '', 'trim');
        if (empty($url)) {
            $this->fail('URL不能为空', 400, Err::INVALID_PARAM);
        }
        $url = urldecode($url);

        // 获取微信配置
        $appId = Env::get('wechat.appid');
        $appSecret = Env::get('wechat.appsecret');

        if (empty($appId) || empty($appSecret)) {
            $this->fail('微信配置不完整', 500, Err::COMMON_ERR);
        }

        // 获取access_token
        $accessToken = $this->getStableAccessToken($appId, $appSecret);
        if (!$accessToken) {
            $this->fail('获取access_token失败', 500, Err::COMMON_ERR);
        }

        // 获取jsapi_ticket
        $jsapiTicket = $this->getJsapiTicket($accessToken);
        if (!$jsapiTicket) {
            $this->fail('获取jsapi_ticket失败', 500, Err::COMMON_ERR);
        }

        // 生成签名
        $timestamp = time();
        $nonceStr = $this->createNonceStr();

        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";
        $signature = sha1($string);

        $config = [
            'appId' => $appId,
            'timestamp' => $timestamp,
            'nonceStr' => $nonceStr,
            'signature' => $signature
        ];

        return $this->success('获取成功', $config);
    }

    /**
     * 根据mediaId下载微信音频文件，转换为MP3并上传到火山云
     *
     * @param Request $request
     * @return \think\Response
     */
    public function downloadAndConvertVoice(Request $request)
    {
        $mediaId = $request->param('media_id', '', 'trim');
        if (empty($mediaId)) {
            $this->fail('media_id不能为空', 400, Err::INVALID_PARAM);
        }

        // 验证mediaId格式（基本验证）
        if (strlen($mediaId) < 10 || strlen($mediaId) > 255) {
            $this->fail('media_id格式不正确', 400, Err::INVALID_PARAM);
        }

        // 获取微信配置
        $appId = Env::get('wechat.appid');
        $appSecret = Env::get('wechat.appsecret');

        if (empty($appId) || empty($appSecret)) {
            $this->fail('微信配置不完整', 500, Err::COMMON_ERR);
        }

        // 获取access_token
        $accessToken = $this->getStableAccessToken($appId, $appSecret);
        if (!$accessToken) {
            $this->fail('获取access_token失败', 500, Err::COMMON_ERR);
        }

        // 下载微信音频文件
        $voiceData = $this->downloadWechatVoice($accessToken, $mediaId);
        if (!$voiceData) {
            $this->fail('下载微信音频文件失败，请检查media_id是否有效且未过期', 500, Err::COMMON_ERR);
        }

        // 保存音频文件到临时目录
        $tempDir = RUNTIME_PATH . 'temp' . DS;
        if (!is_dir($tempDir)) {
            mkdir($tempDir, 0755, true);
        }

        // 检测文件格式
        $fileFormat = $this->detectAudioFormat($voiceData);
        $audioFileName = $tempDir . $mediaId . '.' . $fileFormat;
        $mp3FileName = $tempDir . $mediaId . '.mp3';

        // 写入音频文件
        if (!file_put_contents($audioFileName, $voiceData)) {
            $this->fail('保存音频文件失败', 500, Err::COMMON_ERR);
        }

        // 转换音频到MP3
        $convertResult = $this->convertAudioToMp3($audioFileName, $fileFormat, $mp3FileName);
        if (!$convertResult) {
            // 清理临时文件
            @unlink($audioFileName);
            $this->fail('音频格式转换失败', 500, Err::COMMON_ERR);
        }

        // 上传MP3文件到火山云
        $uploadResult = $this->uploadToVolcengine($mp3FileName);
        if (!$uploadResult) {
            // 清理临时文件
            @unlink($audioFileName);
            @unlink($mp3FileName);
            $this->fail('上传到火山云失败', 500, Err::COMMON_ERR);
        }

        // 清理临时文件
        @unlink($audioFileName);
        @unlink($mp3FileName);

        return $this->success('处理成功', [
            'url' => $uploadResult
        ]);
    }

    /**
     * 下载微信语音文件
     *
     * @param string $accessToken
     * @param string $mediaId
     * @return string|bool
     */
    private function downloadWechatVoice($accessToken, $mediaId)
    {
        $url = "https://api.weixin.qq.com/cgi-bin/media/get?access_token={$accessToken}&media_id={$mediaId}";
        // $url = "https://api.weixin.qq.com/cgi-bin/media/get/jssdk?access_token={$accessToken}&media_id={$mediaId}";
        $response = $this->httpGet($url);
        trace($url, 'info');
        // 检查是否返回了错误信息（JSON格式）
        $result = json_decode($response, true);

        if (isset($result['errcode']) && $result['errcode'] != 0) {
            // 记录详细的错误信息

            trace("下载微信语音文件失败: errcode={$result['errcode']}, errmsg={$result['errmsg']}, media_id={$mediaId}", 'error');
            return false;
        }

        // 返回的是文件二进制数据
        return $response;
    }

    /**
     * 检测音频文件格式
     *
     * @param string $fileData 文件二进制数据
     * @return string 文件格式 (speex, amr, silk, unknown)
     */
    private function detectAudioFormat($fileData)
    {
        // 检查文件头
        $header = substr($fileData, 0, 10);

        // Speex格式检测
        if (strpos($header, 'Speex') !== false) {
            return 'speex';
        }

        // AMR格式检测
        if (strpos($header, '#!AMR') !== false) {
            return 'amr';
        }

        // Silk格式检测 (微信语音常用格式)
        if (strpos($header, '#!SILK_V3') !== false) {
            return 'silk';
        }

        // 默认返回unknown
        return 'unknown';
    }

    /**
     * 检查命令是否存在
     *
     * @param string $command
     * @return bool|string 返回true/false或实际路径
     */
    private function commandExists($command)
    {
        // 使用exec命令检测，避免open_basedir限制
        $whereIsCommand = (PHP_OS == 'WINNT') ? 'where' : 'which';
        $cmd = "$whereIsCommand $command 2>/dev/null";
        exec($cmd, $output, $returnCode);

        if ($returnCode === 0 && !empty($output)) {
            return trim($output[0]);
        }

        // 如果which命令没有找到，尝试直接执行命令检查（不依赖文件存在检查）
        // 不同命令的版本参数可能不同
        $versionFlags = ['-version', '--version', 'version'];
        foreach ($versionFlags as $flag) {
            $testCmd = "$command $flag 2>&1";
            exec($testCmd, $testOutput, $testReturnCode);

            // 检查返回码，0表示成功，1或2可能也表示命令存在但参数不正确
            if ($testReturnCode === 0 || $testReturnCode === 1 || $testReturnCode === 2) {
                return $command; // 返回命令名称，假设它在PATH中
            }
        }

        // 检查项目目录中的符号链接（使用exec执行而不是file_exists）
        $projectBinPath = ROOT_PATH . 'bin' . DS . $command;
        foreach ($versionFlags as $flag) {
            $testProjectCmd = "$projectBinPath $flag 2>&1";
            exec($testProjectCmd, $testProjectOutput, $testProjectReturnCode);

            if ($testProjectReturnCode === 0 || $testProjectReturnCode === 1 || $testProjectReturnCode === 2) {
                return $projectBinPath;
            }
        }

        return false;
    }

    /**
     * 转换音频文件到MP3格式
     *
     * @param string $audioFile 源音频文件路径
     * @param string $format 源音频格式
     * @param string $mp3File 目标MP3文件路径
     * @return bool 转换是否成功
     */
    private function convertAudioToMp3($audioFile, $format, $mp3File)
    {
        // 直接使用项目目录下的FFmpeg路径，避免open_basedir限制
        // $ffmpegPath = ROOT_PATH . 'bin' . DS . 'ffmpeg';
        $ffmpegPath = Env::get('ffmpeg.path', '/usr/local/bin/ffmpeg') ;
        // 通过执行命令来检查FFmpeg是否可用，而不是使用file_exists
        // $hasFfmpeg = $this->isCommandAvailable($ffmpegPath);
        $hasFfmpeg = true;

        // 检查SoX
        $soxPath = $this->commandExists('sox');
        $hasSox = $soxPath !== false;

        trace("hasFfmpeg: {$hasFfmpeg}, ffmpegPath: {$ffmpegPath}, hasSox: {$hasSox}, soxPath: {$soxPath}", 'info');

        if ($hasFfmpeg) {
            // 使用FFmpeg转换
            $cmd = "";
            switch ($format) {
                case 'speex':
                    $cmd = "{$ffmpegPath} -i " . escapeshellarg($audioFile) . " -ar 24000 -ac 1 " . escapeshellarg($mp3File) . " 2>&1";
                    break;
                case 'amr':
                    $cmd = "{$ffmpegPath} -i " . escapeshellarg($audioFile) . " -ar 24000 -ac 1 " . escapeshellarg($mp3File) . " 2>&1";
                    break;
                case 'silk':
                    // SILK需要特殊处理
                    $cmd = "{$ffmpegPath} -i " . escapeshellarg($audioFile) . " -ar 24000 -ac 1 " . escapeshellarg($mp3File) . " 2>&1";
                    break;
                default:
                    // 尝试直接转换
                    $cmd = "{$ffmpegPath} -i " . escapeshellarg($audioFile) . " -ar 24000 -ac 1 " . escapeshellarg($mp3File) . " 2>&1";
                    break;
            }
            trace("FFmpeg command: {$cmd}", 'info');

            exec($cmd, $output, $returnCode);
            if ($returnCode !== 0) {
                trace("FFmpeg转换失败: " . implode("\n", $output), 'error');
            }
            return $returnCode === 0 && file_exists($mp3File);
        } elseif ($hasSox) {
            // 使用SoX转换
            $cmd = "";
            switch ($format) {
                case 'speex':
                    $cmd = "{$soxPath} -t speex " . escapeshellarg($audioFile) . " -t mp3 " . escapeshellarg($mp3File) . " 2>&1";
                    break;
                case 'amr':
                    $cmd = "{$soxPath} -t amr " . escapeshellarg($audioFile) . " -t mp3 " . escapeshellarg($mp3File) . " 2>&1";
                    break;
                default:
                    // 尝试直接转换
                    $cmd = "{$soxPath} " . escapeshellarg($audioFile) . " -t mp3 " . escapeshellarg($mp3File) . " 2>&1";
                    break;
            }
            trace("SoX command: {$cmd}", 'info');

            exec($cmd, $output, $returnCode);
            if ($returnCode !== 0) {
                trace("SoX转换失败: " . implode("\n", $output), 'error');
            }
            return $returnCode === 0 && file_exists($mp3File);
        } else {
            // 如果没有转换工具，尝试使用PHP代码进行简单转换（可能不完美）
            return $this->simpleAudioToMp3($audioFile, $format, $mp3File);
        }
    }

    /**
     * 检查命令是否可用（避免open_basedir限制）
     *
     * @param string $commandPath
     * @return bool
     */
    private function isCommandAvailable($commandPath)
    {
        // 使用exec来测试命令是否存在，避免直接访问文件路径触发open_basedir限制
        $command = "$commandPath -version 2>&1";
        exec($command, $output, $returnCode);
        return $returnCode === 0 || $returnCode === 1;
    }

    /**
     * 简单的音频到MP3转换（仅作示例，实际效果可能不佳）
     *
     * @param string $audioFile
     * @param string $format
     * @param string $mp3File
     * @return bool
     */
    private function simpleAudioToMp3($audioFile, $format, $mp3File)
    {
        // 这里只是一个示例实现，实际应用中建议安装FFmpeg
        // 直接复制文件并修改扩展名（这种方式不能真正转换格式）
        if (copy($audioFile, $mp3File)) {
            // 添加一个标识说明这是未经转换的音频文件重命名为MP3
            file_put_contents($mp3File, "NOTE: This is a {$format} file renamed to MP3 for demonstration purposes.\n", FILE_APPEND);
            return true;
        }
        return false;
    }

    /**
     * 上传文件到火山云
     *
     * @param string $filePath
     * @return string|bool
     */
    private function uploadToVolcengine($filePath)
    {
        // 获取火山云配置
        $config = [
            'accessKeyId' => Env::get('volcengine.access_key_id'),
            'accessKeySecret' => Env::get('volcengine.access_key_secret'),
            'region' => Env::get('volcengine.region', 'cn-beijing'),
            'bucket' => Env::get('volcengine.bucket'),
            'endpoint' => Env::get('volcengine.endpoint')
        ];

        // 检查配置是否完整
        if (
            empty($config['accessKeyId']) || empty($config['accessKeySecret']) ||
            empty($config['bucket']) || empty($config['endpoint'])
        ) {
            // 配置不完整，保存到本地作为示例
            return $this->saveToLocal($filePath);
        }

        // 使用火山云TOS上传
        try {
            // 创建TOS客户端
            $tosClient = new TosClient([
                'region' => $config['region'],
                'ak' => $config['accessKeyId'],
                'sk' => $config['accessKeySecret'],
                'endpoint' => $config['endpoint']
            ]);

            // 生成对象键
            $objectKey = 'voice/' . date('Y/m/d') . '/' . uniqid() . '.mp3';

            // 创建PutObjectInput对象
            $input = new \Tos\Model\PutObjectInput($config['bucket'], $objectKey, fopen($filePath, 'r'));

            // 上传文件
            $result = $tosClient->putObject($input);

            if ($result) {
                // 返回文件URL
                return sprintf('https://%s.%s/%s', $config['bucket'], $config['endpoint'], $objectKey);
            }
        } catch (\Exception $e) {
            // 上传失败，保存到本地作为备选方案
            return $this->saveToLocal($filePath);
        }

        return false;
    }

    /**
     * 保存文件到本地（备选方案）
     *
     * @param string $filePath
     * @return string|bool
     */
    private function saveToLocal($filePath)
    {
        // 读取文件内容
        $fileContent = file_get_contents($filePath);
        if ($fileContent === false) {
            return false;
        }

        // 生成文件名
        $fileName = 'voice/' . date('Y/m/d') . '/' . uniqid() . '.mp3';

        // 保存到本地public目录
        $uploadDir = ROOT_PATH . 'public' . DS . 'uploads' . DS;
        if (!is_dir($uploadDir)) {
            mkdir($uploadDir, 0755, true);
        }

        // 确保子目录存在
        $subDir = dirname($uploadDir . $fileName);
        if (!is_dir($subDir)) {
            mkdir($subDir, 0755, true);
        }

        $uploadPath = $uploadDir . $fileName;
        if (file_put_contents($uploadPath, $fileContent) !== false) {
            // 返回URL
            return '/uploads/' . $fileName;
        }

        return false;
    }

    /**
     * 获取微信access_token
     *
     * @param string $appId
     * @param string $appSecret
     * @return string|bool
     */
    private function getAccessToken($appId, $appSecret)
    {
        $cacheKey = "wx_access_token_{$appId}";
        $accessToken = Cache::get($cacheKey);

        if ($accessToken) {
            return $accessToken;
        }

        // 请求微信接口获取access_token
        $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appSecret}";
        $response = $this->httpGet($url);
        $result = json_decode($response, true);

        if (isset($result['access_token'])) {
            // 缓存access_token，有效期7000秒（微信官方有效期是7200秒，这里提前200秒过期）
            Cache::set($cacheKey, $result['access_token'], 7000);
            return $result['access_token'];
        } else {
            trace($result, 'error');
        }

        return false;
    }

    /**
     * 获取微信稳定版access_token（使用stable_token接口）
     *
     * @param string $appId
     * @param string $appSecret
     * @param bool $forceRefresh 是否强制刷新
     * @return string|bool
     */
    private function getStableAccessToken($appId, $appSecret, $forceRefresh = false)
    {
        $cacheKey = "wx_stable_access_token_{$appId}";
        $accessToken = Cache::get($cacheKey);

        if ($accessToken && !$forceRefresh) {
            return $accessToken;
        }

        // 使用POST方式请求微信stable_token接口获取access_token
        $url = "https://api.weixin.qq.com/cgi-bin/stable_token";
        $postData = [
            'grant_type' => 'client_credential',
            'appid' => $appId,
            'secret' => $appSecret
        ];
        
        // 如果需要强制刷新，添加force_refresh参数
        if ($forceRefresh) {
            $postData['force_refresh'] = true;
        }
        
        $response = $this->httpPost($url, $postData);
        $result = json_decode($response, true);

        if (isset($result['access_token'])) {
            // 缓存access_token，有效期7000秒（微信官方有效期是7200秒，这里提前200秒过期）
            Cache::set($cacheKey, $result['access_token'], 7000);
            return $result['access_token'];
        } else {
            trace($result, 'error');
        }

        return false;
    }

    /**
     * 获取微信jsapi_ticket
     *
     * @param string $accessToken
     * @return string|bool
     */
    private function getJsapiTicket($accessToken)
    {
        $cacheKey = "wx_jsapi_ticket_{$accessToken}";
        $jsapiTicket = Cache::get($cacheKey);

        if ($jsapiTicket) {
            return $jsapiTicket;
        }

        // 请求微信接口获取jsapi_ticket
        $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$accessToken}&type=jsapi";
        $response = $this->httpGet($url);
        $result = json_decode($response, true);

        if (isset($result['ticket'])) {
            // 缓存jsapi_ticket，有效期7000秒
            Cache::set($cacheKey, $result['ticket'], 7000);
            return $result['ticket'];
        }

        return false;
    }

    /**
     * 生成随机字符串
     *
     * @param int $length
     * @return string
     */
    private function createNonceStr($length = 16)
    {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * 发起GET请求
     *
     * @param string $url
     * @return string
     */
    private function httpGet($url)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        $response = curl_exec($curl);
        curl_close($curl);

        return $response;
    }

    /**
     * 发起POST请求
     *
     * @param string $url
     * @param array $data
     * @return string
     */
    private function httpPost($url, $data = [])
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($curl, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json'
        ]);
        $response = curl_exec($curl);
        curl_close($curl);

        return $response;
    }
}
